package org.internna.ossmoney.model;

import java.util.Date;
import java.util.List;
import java.util.ArrayList;
import java.math.BigDecimal;
import javax.persistence.Query;
import javax.persistence.OneToOne;
import javax.persistence.Temporal;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.CascadeType;
import javax.persistence.TemporalType;
import javax.validation.constraints.NotNull;
import org.internna.ossmoney.util.DateUtils;
import org.internna.ossmoney.util.StringUtils;
import org.internna.ossmoney.model.InvestmentTransaction;
import org.springframework.util.CollectionUtils;
import org.springframework.context.MessageSource;
import org.springframework.roo.addon.tostring.RooToString;
import org.springframework.roo.addon.javabean.RooJavaBean;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.roo.addon.jpa.entity.RooJpaEntity;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.roo.addon.serializable.RooSerializable;
import org.springframework.roo.addon.jpa.activerecord.RooJpaActiveRecord;

import static org.springframework.util.StringUtils.hasText;

@RooJavaBean
@RooJpaEntity
@RooSerializable
@RooJpaActiveRecord
@RooToString(excludeFields = {"messageSource"})
@NamedQuery(name = "AccountTransaction.findUnreconciled", query = "SELECT a FROM AccountTransaction a WHERE account = :account AND reconciled = false")
public class AccountTransaction extends AbstractEntity implements Comparable<AccountTransaction> {

	private static final long serialVersionUID = -1231932522426899599L;

    private static final String FIND_UNRECONCILED = "AccountTransaction.findUnreconciled";

    @NotNull
    private BigDecimal amount;

    private String memo;

    private String referenceNumber;

    private String originOfTheFunds;

    @NotNull
    @Temporal(TemporalType.TIMESTAMP)
    @DateTimeFormat(style = "S-")
    private Date operationDate;

    @NotNull
    @ManyToOne
    private Payee payee;

    @NotNull
    @ManyToOne
    private Account account;

    @NotNull
    @ManyToOne(fetch = FetchType.EAGER)
    private Subcategory subcategory;

    @NotNull
    private Boolean reconciled = Boolean.FALSE;

    @OneToOne(cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, fetch = FetchType.EAGER, mappedBy = "accountTransaction")
    private InvestmentTransaction investment;


    @Override
    public final int compareTo(AccountTransaction other) {
        return other == null ? 1 : operationDate == null ? other.operationDate == null ? 0 : -1 : other.operationDate == null ? 1 : operationDate.compareTo(other.operationDate);
    }

    public void setAccount(Account account) {
        if ((account != null) && (account != getAccount())) {
            this.account = account;
            account.addTransaction(this);
        }
    }

    public static final AccountTransaction createInstance(Account account, Payee payee, Subcategory subcategory, BigDecimal amount, Date operationDate, String memo) {
        AccountTransaction transaction = new AccountTransaction();
        transaction.setPayee(payee);
        transaction.setAmount(amount);
        transaction.setAccount(account);
        transaction.setSubcategory(subcategory);
        if ((subcategory != null) && (amount != null)) {
            Category category = subcategory.getParentCategory();
            if (category != null) {
                transaction.setAmount(amount.abs().multiply(new BigDecimal(category.isIncome() ? 1 : -1)));
            }
        }
        if (hasText(memo)) {
            transaction.setMemo(memo);
        }
        transaction.setOperationDate(operationDate);
        return transaction;
    }

    @SuppressWarnings("unchecked")
    public static final List<AccountTransaction> findUnreconciledTransactions(Account account) {
        Query query = entityManager().createNamedQuery(FIND_UNRECONCILED, AccountTransaction.class);
        query.setParameter("account", account);
        return filterTransfers(query.getResultList());
    }

    public static List<AccountTransaction> filterTransfers(List<AccountTransaction> data) {
        List<AccountTransaction> filtered = new ArrayList<AccountTransaction>();
        if (!CollectionUtils.isEmpty(data)) {
            for (AccountTransaction transaction : data) {
                String category = transaction.subcategory.getCategory();
                if (!category.startsWith("category.transfer") & !category.startsWith("category.cc.payment") & !category.startsWith("category.investment")) {
                    filtered.add(transaction);
                }
            }
        }
        return filtered;
    }

    public final String asQIF(MessageSource messageSource) {
        StringBuilder qif = new StringBuilder();
        qif.append(DateUtils.asQIF(operationDate)).append("\n");
        if (account.isCreditCard() && reconciled) {
            qif.append("CX\n");
        }
        if (hasText(memo)) {
            qif.append("M").append(memo).append("\n");
        }
        qif.append("T").append(StringUtils.format(amount)).append("\n");
        if (investment != null) {
            qif.append("N").append(StringUtils.getOperation(subcategory)).append("\n");
            qif.append("Y").append(investment.getInvestment().getName()).append("\n");
            qif.append("I");
            if (investment.getPrice() != null) {
                qif.append(StringUtils.format(investment.getPrice().getPrice()))
                	.append("\nQ").append(StringUtils.format(investment.getQuantity())).append("\n");
            } else {
                qif.append("\nQ\n");
            }
        } else {
            qif.append("P").append(payee.getName()).append("\n");
        }
        qif.append("L");
        if ("category.movement".equals(subcategory.getParentCategory().getCategory())) {
            qif.append("[").append(subcategory.getCategory().endsWith("in") ? hasText(originOfTheFunds) ? originOfTheFunds : account.getName() : getPayee().getName()).append("]");
        } else {
            qif.append(messageSource == null ? subcategory.getParentCategory().getCategory() : messageSource.getMessage(subcategory.getParentCategory().getCategory(), null, subcategory.getParentCategory().getCategory(), account.getLocale())).append(":").append(messageSource == null ? subcategory.getCategory() : messageSource.getMessage(subcategory.getCategory(), null, subcategory.getCategory(), account.getLocale()));
        }
        return qif.append("\n^\n").toString();
    }

    @Transactional
    public void persist() {
        operationDate = DateUtils.getRandomTimeDate(operationDate);
        if (this.entityManager == null) this.entityManager = entityManager();
        this.entityManager.persist(this);
    }

    public BigDecimal getBalance() {
        return account != null ? account.calculateBalance(DateUtils.nextDate(operationDate)) : BigDecimal.ZERO;
    }

    public boolean isInvestmentTransaction() {
        return investment != null;
    }

}
